package com.song.article.service.impl;

import com.github.pagehelper.PageHelper;
import com.mongodb.client.gridfs.GridFSBucket;
import com.song.api.BaseService;
import com.song.api.config.MyServiceList;
import com.song.api.config.RabbitMQConfig;
import com.song.api.config.RabbitMQDelayConfig;
import com.song.api.controller.article.ArticlePortalControllerApi;
import com.song.article.mapper.ArticleMapper;
import com.song.article.mapper.ArticleMapperCustom;
import com.song.article.service.ArticleService;
import com.song.enums.ArticleAppointType;
import com.song.enums.ArticleReviewLevel;
import com.song.enums.ArticleReviewStatus;
import com.song.enums.YesOrNo;
import com.song.exception.GraceException;
import com.song.grace.result.GraceJSONResult;
import com.song.grace.result.ResponseStatusEnum;
import com.song.pojo.Article;
import com.song.pojo.Category;
import com.song.pojo.bo.NewArticleBO;
import com.song.pojo.eo.ArticleEO;
import com.song.pojo.vo.ArticleDetailVO;
import com.song.utils.JsonUtils;
import com.song.utils.PagedGridResult;
import com.song.utils.extend.TXTextReviewUtils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.n3r.idworker.Sid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import tk.mybatis.mapper.entity.Example;

import java.io.File;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ArticleServiceImpl extends BaseService implements ArticleService {

    private static final Logger LOG = LoggerFactory.getLogger(ArticleServiceImpl.class);
    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private ArticleMapperCustom articleMapperCustom;

    @Autowired
    private Sid sid;

    @Autowired
    private TXTextReviewUtils txTextReviewUtils;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private GridFSBucket gridFSBucket;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Autowired
    private ArticlePortalControllerApi articlePortalControllerApi;

    /**
     * 文章发布
     * @param newArticleBO
     * @param category
     */
    @Transactional
    @Override
    public void createArticle(NewArticleBO newArticleBO, Category category) {
        // 设置id
        String articleId = sid.nextShort();
        Article article = new Article();
        BeanUtils.copyProperties(newArticleBO, article);

        article.setId(articleId);
        article.setCategoryId(category.getId());
        article.setArticleStatus(ArticleReviewStatus.REVIEWING.type);
        article.setCommentCounts(0);
        article.setReadCounts(0);
        article.setIsDelete(YesOrNo.NO.type);
        article.setCreateTime(new Date());
        article.setUpdateTime(new Date());

        if (article.getIsAppoint().equals(ArticleAppointType.TIMING.type)) {
            article.setPublishTime(newArticleBO.getPublishTime());
        } else if (article.getIsAppoint().equals(ArticleAppointType.IMMEDIATELY.type)) {
            article.setPublishTime(new Date());
        }

        int result = articleMapper.insert(article);
        if (result != 1) {
            GraceException.display(ResponseStatusEnum.ARTICLE_CREATE_ERROR);
        }
        // 发送延迟消息到mq，计算发布时间和当前时间差，则为往后延迟的时间
        if (article.getIsAppoint().equals(ArticleAppointType.TIMING.type)) {
            Date endDate = newArticleBO.getPublishTime();
            Date startDate = new Date();
            // 计算延迟时间
            int delayTimes = (int) (endDate.getTime() - startDate.getTime());
//            System.out.println(DateUtil.timeBetween(startDate, endDate));

            // FIXME: 为了测试方便写死 10s
            // int delayTimes = 10 * 1000;
            // 设置延迟消息时间
            MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
                @Override
                public Message postProcessMessage(Message message) throws AmqpException {
                    // 设置持久
                    message.getMessageProperties()
                            .setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                    // 设置延迟的时间，单位毫秒
                    message.getMessageProperties().setDelay(delayTimes);
                    return message;
                }
            };
            // 发送延迟消息
            rabbitTemplate.convertAndSend(
                    RabbitMQDelayConfig.EXCHANGE_DELAY,
                    "publish.delay.display",
                    articleId,
                    messagePostProcessor);
            System.out.println("延迟消息-定时发布：" + new Date());

        }

        // 腾讯天御进行文本自动检测
//        String reviewResult = txTextReviewUtils.reviewTextContent(newArticleBO.getTitle() + newArticleBO.getContent());
        // FIXME: 写死，需要人工审核
        String reviewResult = ArticleReviewLevel.REVIEW.type;
        LOG.info("检测结果：" + reviewResult);

        if (ArticleReviewLevel.PASS.type.equalsIgnoreCase(reviewResult)) {
            LOG.info("审核通过");
            // 修改文章状态为审核通过
            this.updateArticleStatus(articleId, ArticleReviewStatus.SUCCESS.type);
            // 审核成功，生成html
            try {
                // 文章生成HTML存储到GridFS中,放回GridFSId
                String articleMongoId = createArticleHTMLToGridFS(articleId);
                // GridFSId存储到文章信息中
                updateArticleToGridFS(articleId, articleMongoId);
                // 发起消息到MQ队列，让消费者监听并执行下载html
                doDownloadArticleHTMLByMQ(articleId,articleMongoId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else if (ArticleReviewLevel.REVIEW.type.equalsIgnoreCase(reviewResult)) {
            LOG.info("需要人工复审");
            // 修改文章状态为需要人工复审
            this.updateArticleStatus(articleId, ArticleReviewStatus.WAITING_MANUAL.type);
        } else if (ArticleReviewLevel.BLOCK.type.equalsIgnoreCase(reviewResult)) {
            LOG.info("审核不通过");
            // 修改文章状态为审核不通过
            this.updateArticleStatus(articleId, ArticleReviewStatus.FAILED.type);
        }
    }

    /**
     * (测试)将定时发布改为即时发布
     */
    @Transactional
    @Override
    public void updateAppointToPublish() {
        articleMapperCustom.updateAppointToPublish();
    }

    /**
     * MQ根据文章id将定时发布改即时发布
     * @param articleId
     */
    @Transactional
    @Override
    public void updateArticleToPublish(String articleId) {
        Article article = new Article();
        article.setId(articleId);
        article.setIsAppoint(ArticleAppointType.IMMEDIATELY.type);
        articleMapper.updateByPrimaryKeySelective(article);

        // 在定时的延迟队列中执行，审核通过后则可以直接存入es中
        Article article1 = articleMapper.selectByPrimaryKey(articleId);
        // 如果审核通过，则查询article，存入es中
        if (article1.getArticleStatus().equals(ArticleReviewStatus.SUCCESS.type)) {
            // 存入es中
            if(article1.getIsAppoint().equals(ArticleAppointType.IMMEDIATELY.type)) {
                ArticleEO articleEO = new ArticleEO();
                BeanUtils.copyProperties(article1,articleEO);
                IndexQuery iq = new IndexQueryBuilder().withObject(articleEO).build();
                esTemplate.index(iq);
            }
        }
    }

    /**
     * 查询用户发布的所有文章列表
     * @param userId
     * @param keyword
     * @param status
     * @param startDate
     * @param endDate
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public PagedGridResult queryMyArticleList(String userId,
                                              String keyword,
                                              Integer status,
                                              Date startDate,
                                              Date endDate,
                                              Integer page,
                                              Integer pageSize) {

        Example example = new Example(Article.class);
        example.orderBy("createTime").desc();
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("publishUserId", userId);

        if (StringUtils.isNotBlank(keyword)) {
            criteria.andLike("title", "%" + keyword + "%");
        }
        if (ArticleReviewStatus.isArticleStatusValid(status)) {
            criteria.andEqualTo("articleStatus", status);
        }
        // 审核中是机审和人审核的两个状态，所以需要单独判断
        if (status != null && status == 12) {
            criteria.andEqualTo("articleStatus", ArticleReviewStatus.REVIEWING.type)
                    .orEqualTo("articleStatus", ArticleReviewStatus.WAITING_MANUAL.type);
        }
        if (startDate != null) {
            criteria.andGreaterThanOrEqualTo("publishTime", startDate);
        }
        if (endDate != null) {
            criteria.andLessThanOrEqualTo("publishTime", endDate);
        }

        //isDelete 必须是0
        criteria.andEqualTo("isDelete", YesOrNo.NO.type);

        /**
         * page: 第几页
         * pageSize: 每页显示条数
         */
        PageHelper.startPage(page, pageSize);
        List<Article> list = articleMapper.selectByExample(example);

        return setPagedGridResult(list, page);
    }

    /**
     * 更改文章状态
     * @param articleId
     * @param pendingStatus
     */
    @Transactional
    @Override
    public void updateArticleStatus(String articleId, Integer pendingStatus) {
        Example example = new Example(Article.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("id", articleId);

        Article article = new Article();
        article.setArticleStatus(pendingStatus);

        int result = articleMapper.updateByExampleSelective(article, example);
        if (result != 1) {
            GraceException.display(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
        }

        // 如果审核通过，则查询article，存入es中
        if (pendingStatus.equals(ArticleReviewStatus.SUCCESS.type)) {
            Article article1 = articleMapper.selectByPrimaryKey(articleId);
            // 如果是即时发布的文章，审核通过后则可以直接存入es中
            if(article1.getIsAppoint().equals(ArticleAppointType.IMMEDIATELY.type)) {
                ArticleEO articleEO = new ArticleEO();
                BeanUtils.copyProperties(article1,articleEO);
                IndexQuery iq = new IndexQueryBuilder().withObject(articleEO).build();
                esTemplate.index(iq);
            }
        }
    }

    /**
     * 关联文章信息和gridfs中html文件id(Mongo主键id)
     * @param articleId
     * @param articleMongoId
     */
    @Transactional
    @Override
    public void updateArticleToGridFS(String articleId, String articleMongoId) {
        Article article = new Article();
        article.setId(articleId);
        article.setMongoFileId(articleMongoId);

        int result = articleMapper.updateByPrimaryKeySelective(article);
        if (result != 1) {
            GraceException.display(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
        }
    }

    /**
     * 查询所有文章列表
     * @param status
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public PagedGridResult queryAllArticleList(Integer status, Integer page, Integer pageSize) {
        Example example = new Example(Article.class);
        example.orderBy("createTime").desc();
        Example.Criteria criteria = example.createCriteria();

        if (ArticleReviewStatus.isArticleStatusValid(status)) {
            criteria.andEqualTo("articleStatus", status);
        }

        // 审核中是机审和人审核的两个状态，所以需要单独判断
        if (status != null && status == 12) {
            criteria.andEqualTo("articleStatus", ArticleReviewStatus.REVIEWING.type)
                    .orEqualTo("articleStatus", ArticleReviewStatus.WAITING_MANUAL.type);
        }
        //isDelete 必须是0
        criteria.andEqualTo("isDelete", YesOrNo.NO.type);

        PageHelper.startPage(page, pageSize);
        List<Article> list = articleMapper.selectByExample(example);
        return setPagedGridResult(list, page);
    }

    /**
     * 删除文章
     * @param userId
     * @param articleId
     */
    @Transactional
    @Override
    public void deleteArticle(String userId, String articleId) {
        Example example = makeExampleCriteria(userId, articleId);
        Article article = new Article();
        article.setIsDelete(YesOrNo.YES.type);

        int result = articleMapper.updateByExampleSelective(article, example);
        if (result != 1) {
            GraceException.display(ResponseStatusEnum.ARTICLE_DELETE_ERROR);
        }

        // MQ中删除文章HTML
        deleteHTML(articleId);
        // es中删除该文章信息
        esTemplate.delete(ArticleEO.class,articleId);

    }

    /**
     * 撤回文章
     * @param userId
     * @param articleId
     */
    @Transactional
    @Override
    public void withdrawArticle(String userId, String articleId) {
        Example example = makeExampleCriteria(userId, articleId);
        Article article = new Article();
        article.setArticleStatus(ArticleReviewStatus.WITHDRAW.type);

        int result = articleMapper.updateByExampleSelective(article, example);
        if (result != 1) {
            GraceException.display(ResponseStatusEnum.ARTICLE_WITHDRAW_ERROR);
        }

        // MQ中删除文章HTML
        deleteHTML(articleId);
        // es中删除该文章信息
        esTemplate.delete(ArticleEO.class,articleId);

    }

    public Example makeExampleCriteria(String userId, String articleId){
        Example example = new Example(Article.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("publishUserId",userId);
        criteria.andEqualTo("id", articleId);
        return example;
    }

    /**
     * 文章撤回删除后，删除静态化的html
     */
    private void deleteHTML(String articleId) {
        // 1. 查询文章的mongoFileId
        Article pending = articleMapper.selectByPrimaryKey(articleId);

        if (StringUtils.isBlank(pending.getMongoFileId())) {
            return;
        }
        String articleMongoId = pending.getMongoFileId();

        // 2. 删除GridFS上的文件
        gridFSBucket.delete(new ObjectId(articleMongoId));

        // 3. 删除消费端的HTML文件
        doDeleteArticleHTMLByMQ(articleId);
    }
    /**
     * 生成文章HTML存储到GridFS中
     * @param articleId
     * @return
     * @throws Exception
     */
    public String createArticleHTMLToGridFS(String articleId) throws Exception {
        // 0.配置freemarker基本环境
        Configuration cfg = new Configuration(Configuration.getVersion());
        // 声明freemarker模板所需加载的目录的位置
        String classpath = this.getClass().getResource("/").getPath();
        cfg.setDirectoryForTemplateLoading(new File(classpath + "templates"));

        // 1.获得现有的模板ftl文件
        Template template = cfg.getTemplate("detail.ftl", "utf-8");

        // 2.获得动态数据
        ArticleDetailVO articleVO = getArticleDetail(articleId);

        Map<String, Object> map = new HashMap<>();
        map.put("articleDetail", articleVO);

        //获取html内容
        String htmlContent = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
//        System.out.println(htmlContent);

        InputStream inputStream = IOUtils.toInputStream(htmlContent);

        ObjectId fileId = gridFSBucket.uploadFromStream(articleVO.getId() + ".html", inputStream);

        return fileId.toString();
    }

    /**
     * 发起RabbitMQ下载文章HTML
     * @param articleId
     * @param articleMongoId
     */
    private void doDownloadArticleHTMLByMQ(String articleId, String articleMongoId) {
        rabbitTemplate.convertAndSend(
                RabbitMQConfig.EXCHANGE_ARTICLE,
                "article.download.do",
                articleId + "," + articleMongoId);
    }

    /**
     * 发起RabbitMQ删除文章HTML
     * @param articleId
     */
    private void doDeleteArticleHTMLByMQ(String articleId) {
        rabbitTemplate.convertAndSend(
                RabbitMQConfig.EXCHANGE_ARTICLE,
                "article.delete.do",
                articleId);
    }
    
    /**
     * 发起远程调用，请求文章服务根据Id获取文章详情信息
     * @param articleId
     * @return
     */
    public ArticleDetailVO getArticleDetail(String articleId) {
        String url = "http://"
                + MyServiceList.SERVICE_ARTICLE
                + "/portal/article/detail?articleId="
                + articleId;
        ResponseEntity<GraceJSONResult> responseEntity = restTemplate.getForEntity(url, GraceJSONResult.class);
        GraceJSONResult bodyResult = responseEntity.getBody();
        ArticleDetailVO articleVO = null;
        if (bodyResult.getStatus() == 200) {
            String detailJson = JsonUtils.objectToJson(bodyResult.getData());
            articleVO = JsonUtils.jsonToPojo(detailJson, ArticleDetailVO.class);
        }
        return articleVO;
    }
    
}